Introduction to Point Layer
What is a point layer?
If measures are assigned to both of the axes, Muze renders a layer with point mark. Each point in the following chart can take any continuous value in x and y coordinate. It forms the basis of scatter and bubble charts.
Example
const { muze, getDataFromSearchQuery } = viz;
const data = getDataFromSearchQuery();
muze
.canvas()
.rows(["Displacement"])
.columns(["Horsepower"])
.detail(["Name"])
.data(data)
.mount("#chart");
NOTE: You can apply color, shape and size retinal encodings on point layer.
Creating a scatter plot
Mapping two measure(quantitative) fields with a dimension as detail field produces a scatter plot.
Example
const { muze, getDataFromSearchQuery } = viz;
const data = getDataFromSearchQuery();
muze
.canvas()
.rows(["Acceleration"])
.columns(["Horsepower"])
.detail(["Name"])
.data(data)
.mount("#chart");
Creating a scatter plot matrix
Adding on to the previous scatter plot, we plot 3 fields both on rows and columns and color the chart by Cylinders field to get a scatter plot matrix.
Example
const { muze, getDataFromSearchQuery } = viz;
const data = getDataFromSearchQuery();
muze
.canvas()
.rows(["Acceleration", "Horsepower", "Miles_per_Gallon"])
.columns(["Miles_per_Gallon", "Horsepower", "Acceleration"])
.detail(["Name"])
.color("Cylinders")
.data(data)
.mount("#chart");
Creating a dotplot chart
Below is a dot-plot for acceleration over the years colored by Origin.
Example
const { muze, getDataFromSearchQuery } = viz;
const data = getDataFromSearchQuery();
muze
.canvas()
.rows(["Acceleration"])
.columns(["Year"])
.color("Origin")
.detail(["Name"])
.layers([
{
mark: "point",
},
])
.config({
interaction: {
behaviours: {
highlight: {
exact: true,
},
},
},
})
.data(data)
.mount("#chart");
Creating a scatter plot with custom shape
Example
const { muze, getDataFromSearchQuery } = viz;
const data = getDataFromSearchQuery();
let dm = new DataModel(data);
const html = muze.Operators.html;
const smileys = {
smiley1:
'<g class="smiley smiley-0" fill="#fffae6" transform="translate(-26,-26)"><circle r="22" cx="26" cy="26"></circle><g class="smiley-expression" transform="translate(4,4) scale(1.4666666666666666)"><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -7.673427e-02 7.712884e-02)" class="st0" cx="15" cy="15" rx="15" ry="15"></ellipse><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.115256e-02 0.1111)" class="st1" cx="21.6" cy="10" rx="1.1" ry="1.1"></ellipse><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.167323e-02 4.345998e-02)" class="st1" cx="8.4" cy="10.1" rx="1.1" ry="1.1"></ellipse><line class="st2" x1="20" y1="22.2" x2="11.5" y2="22.2"></line></g></g>',
smiley2:
'<g class="smiley smiley-1" fill="#ffefab" transform="translate(-26,-26)"><circle r="22" cx="26" cy="26"></circle><g class="smiley-expression" transform="translate(4,4) scale(1.4666666666666666)"><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -7.673427e-02 7.712884e-02)" class="st5" cx="15" cy="15" rx="15" ry="15"></ellipse><path class="st2" d="M6.8,17c2.5,4.5,8.3,6.1,12.8,3.6c1.3-0.7,2.4-1.7,3.2-3"></path><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.115328e-02 0.1109)" class="st1" cx="21.6" cy="10" rx="1.1" ry="1.1"></ellipse><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.167395e-02 4.317867e-02)" class="st1" cx="8.4" cy="10.1" rx="1.1" ry="1.1"></ellipse></g></g>',
smiley3:
'<g class="smiley smiley-2" fill="#ffe473" transform="translate(-26,-26)"><circle r="22" cx="26" cy="26"></circle><g class="smiley-expression" transform="translate(4,4) scale(1.4666666666666666)"><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -7.673427e-02 7.712884e-02)" class="st5" cx="15" cy="15" rx="15" ry="15"></ellipse><path class="st2" d="M6.8,17c2.5,4.5,8.3,6.1,12.8,3.6c1.3-0.7,2.4-1.7,3.2-3"></path><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.115328e-02 0.1109)" class="st1" cx="21.6" cy="10" rx="1.1" ry="1.1"></ellipse><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.167395e-02 4.317867e-02)" class="st1" cx="8.4" cy="10.1" rx="1.1" ry="1.1"></ellipse></g></g>',
smiley4:
'<g class="smiley smiley-3" fill="#ffcc00" transform="translate(-26,-26)"><circle r="22" cx="26" cy="26"></circle><g class="smiley-expression" transform="translate(4,4) scale(1.4666666666666666)"><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -7.673643e-02 7.684924e-02)" class="st6" cx="14.9" cy="15" rx="15" ry="15"></ellipse><path class="st7" d="M5.6,15.1c0,5.2,4.2,9.4,9.4,9.3c5.2,0,9.4-4.2,9.3-9.4"></path><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.115504e-02 0.1109)" class="st1" cx="21.6" cy="10" rx="1.1" ry="1.1"></ellipse><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.167570e-02 4.318081e-02)" class="st1" cx="8.4" cy="10.1" rx="1.1" ry="1.1"></ellipse></g></g>',
smiley5:
'<g class="smiley smiley-4" fill="#eabe03" transform="translate(-26,-26)"><circle r="22" cx="26" cy="26"></circle><g class="smiley-expression" transform="translate(4,4) scale(1.4666666666666666)"><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -7.673427e-02 7.712884e-02)" class="st6" cx="15" cy="15" rx="15" ry="15"></ellipse><path class="st2" d="M5.6,15.1c0,5.2,4.2,9.4,9.4,9.3c5.2,0,9.4-4.2,9.3-9.4L5.6,15.1z"></path><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.115260e-02 0.1109)" class="st1" cx="21.6" cy="10" rx="1.1" ry="1.1"></ellipse><ellipse transform="matrix(1 -5.128771e-03 5.128771e-03 1 -5.167491e-02 4.317867e-02)" class="st1" cx="8.4" cy="10.1" rx="1.1" ry="1.1"></ellipse></g></g>',
};
const stateGrid = [
[null, null, null, null, null, "WI", null, null, null, "VT", "ME"],
["WA", "ID", "MT", "ND", "MN", "IL", "MI", null, "NY", "MA", "NH"],
["OR", "NV", "WY", "SD", "IN", "IA", "OH", "PA", "NJ", "CT", "RI"],
["CA", "UT", "CO", "NE", "MO", "KY", "WV", "VA", "MD", "DE", null],
[null, "AZ", "NM", "KS", "AR", "TN", "NC", "SC", null, null, null],
[null, null, null, "OK", "LA", "MS", "AL", "GA", null, null, null],
["HI", "AK", null, "TX", null, null, null, null, "FL", null, null],
];
const alphabets = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"];
dm = dm.calculateVariable(
{
name: "Latiudinal-Dimension",
type: "dimension",
},
["State Codes"],
(state) => {
let x = -1;
stateGrid.forEach((e, i) => {
if (e.indexOf(state) > -1) {
x = `${i}`;
}
});
return alphabets[x];
}
);
dm = dm.calculateVariable(
{
name: "Longitudinal-Dimension",
type: "dimension",
},
["State Codes"],
(state) => {
let x = -1;
stateGrid.forEach((e) => {
if (e.indexOf(state) > -1) {
x = `${e.indexOf(state)}`;
}
});
return alphabets[x];
}
);
dm = dm.sort([["Well-being Index"]]);
let wellBeingArray = dm.getField("Well-being Index").data();
const quantiles = Math.round(wellBeingArray.length / 5);
dm = dm.calculateVariable(
{
name: "Well-being-quantile",
type: "dimension",
},
["Well-being Index"],
(wbIndex) => {
const index = wellBeingArray.indexOf(wbIndex);
return Math.floor(index / quantiles);
}
);
dm = dm.sort([
["Longitudinal-Dimension", "asc"],
["Latiudinal-Dimension", "asc"],
]);
muze
.canvas()
.rows(["Latiudinal-Dimension"])
.columns(["Longitudinal-Dimension"])
.shape("Well-being-quantile")
.detail(["State Codes", "State", "Well-being Index"])
.layers([
{
mark: "point",
encoding: {
color: {
value: () => null,
},
},
interaction: {
focus: {
style: {
fill: null,
},
},
},
},
{
mark: "text",
encoding: {
text: "State Codes",
color: {
value: () => "rgba(0,0,0,.4)",
},
},
encodingTransform: (points) => {
points.forEach((e) => {
e.update.y += 30;
});
return points;
},
},
])
.config({
gridLines: {
y: {
show: true,
},
},
border: {
showValueBorders: {
left: false,
bottom: false,
},
},
axes: {
y: {
domain: ["a", "b", "c", "d", "e", "f", "g"].reverse(),
show: false,
},
x: {
show: false,
},
},
gridLines: {
y: {
show: false,
},
},
interaction: {
tooltip: {
formatter: (dataInfo) => {
const { dataModel } = dataInfo;
const tooltipData = dataModel.getData().data;
// const fieldConfig = dataModel.getFieldsConfig();
let tooltipContent = "";
tooltipData.forEach((dataArray) => {
const state = dataArray[dataModel.getFieldIndex("State")];
const wellBeing =
dataArray[dataModel.getFieldIndex("Well-being Index")];
const quant =
dataArray[dataModel.getFieldIndex("Well-being-quantile")];
const wellBeingTypes = [
"Poor",
"Less Than Average",
"Average",
"Above Average",
"Excellent",
];
tooltipContent += `<p>The state of <b>${state}</b> has a well being score of <b>${wellBeing}</b>
providing a <b>${wellBeingTypes[quant]}</b> quality of life</p>`;
});
return html`${tooltipContent}`;
},
},
},
legend: {
position: "bottom",
shape: {
domain: ["0", "1", "2", "3", "4"],
generator: (val) => {
const pathVar = document.createElementNS(
"http://www.w3.org/2000/svg",
"g"
);
pathVar.innerHTML = smileys[`smiley${+val + 1}`];
pathVar.setAttribute("class", "smiley-icon");
pathVar.setAttribute("stroke", "black");
pathVar.setAttribute("stroke-width", "1");
return pathVar;
},
title: { text: "Well Being Score" },
interaction: {
highlight: {
target: {
layer: {
highlight: {
sideEffects: {
"plot-highlighter": {
enabled: false,
},
},
},
},
},
sideEffects: {
"legend-highlighter": {
rules: [
{
target: "entrySet",
className: "muze-legend-brighten",
},
],
},
},
},
},
item: {
icon: {
height: 40,
width: 40,
},
text: {
formatter: () => "",
orientation: "bottom",
},
},
},
},
})
.title("Where to find the good life in USA?", { align: "center" })
.subtitle("An example of Custom Shapes provided in Shape Encoding", {
align: "center",
})
.data(dm)
.width(830)
.height(650)
.mount("#chart");
The sample above may seem intimidating, so let's quickly run over it:
- First, we add an object named smileys with 5 distinct svg groups with the shape we want
- Next, we calculate two fields Latiudinal-Dimension and Longitudinal-Dimension which just assigns an alphabet to each row in the data
- We then go on creating the chart with a custom shape using the shape generator api
- We remove border, axes, gridlines and also place the legend on bottom
- The tooltip is also customized using the tooltip's formatter function